#include <QtGui>
 #include <QtOpenGL>

 #include <math.h>

#define QT_USE_FAST_CONCATENATION

 #include "glwidget.h"
 #include "qtlogo.h"
#include "Primitive.h"
#include "Cube.h"
#include "qmatrix4x4.h"

 #ifndef GL_MULTISAMPLE
 #define GL_MULTISAMPLE  0x809D
 #endif
#define min(a,b) (((a) < (b)) ? (a) : (b))

namespace CAD_CAM
{

	GLWidget::GLWidget(const QGLFormat & format)
		: QGLWidget(format), delta(0.05f), rPlane(1.0f), leftColor(90,0,0), rightColor(0,90,0), eyesWidth(0.2f), eyePosition(0, 0, -1)
	 {
		 torus = QSharedPointer<Torus>(new Torus());
	 
		 cursor = new SimpleCursor();

		 cursor->setHeight(0.01);
		 cursor->setWidth(0.01);
		 cursor->setLength(0.01);

		 torus.value->setHeight(0.1);
		 torus.value->setWidth(0.1);
		 torus.value->setLength(0.1);

		 mProjectionMatrix(3,2) = rPlane;
		 mProjectionMatrix(3,3)=0;
		 //mProjectionMatrix.translate(-eyePosition);
		 stereo = false;

		setAutoFillBackground(false);
		 setMinimumSize(200, 200);
		 setWindowTitle(tr("Overpainting a Scene"));

		cursorStep = 0.01;

		isSelected = false;
		selectedIndex = -1;
	 }

	 void GLWidget::showEvent(QShowEvent *event)
	 {
		 Q_UNUSED(event);
	 }

	 GLWidget::~GLWidget()
	 {
		 delete cursor;
	 }

	void GLWidget::timeout(  )
	{
	}

	 QSize GLWidget::minimumSizeHint() const
	 {
		 return QSize(400, 400);
	 }

	 QSize GLWidget::sizeHint() const
	 {
		 return QSize(800, 600);
	 }

	 static void qNormalizeAngle(int &angle)
	 {

		 while (angle < 0)
			 angle += 360 * 16;
		 while (angle > 360 * 16)
			 angle -= 360 * 16;
	 }

	 void GLWidget::setXRotation(int angle)
	 {
		 //torus.rotate(angle, 1.0, 0.0, 0.0);
		 mViewMatrix.rotate(angle/10.0, 1.0, 0.0, 0.0);
		 update();
	 }

	 void GLWidget::setYRotation(int angle)
	 { 
		 //torus.rotate(angle, 0.0, 1.0, 0.0);
		 mViewMatrix.rotate(angle/10.0, 0.0, 1.0, 0.0);
		 update();
	 }

	 void GLWidget::setZRotation(int angle)
	 {
		 //torus.rotate(angle, 0.0, 0.0, 1.0);
		 mViewMatrix.rotate(angle/10.0, 0.0, 0.0, 1.0);
		 update();
	 }

	 void GLWidget::setRPlane(int R)
	 {
		 rPlane = (float)R/100.0f;
		 mProjectionMatrix(3,2) = 1.0f/rPlane;
		 update();
	 }

	 void GLWidget::initializeGL()
	 {

		 qglClearColor(QColor(0, 0, 0));
		 glDisable(GL_DEPTH_TEST);

		 torus.value->initBuffers();

		 prepareShaders();
	 }

	 void GLWidget::paintEvent(QPaintEvent *event)
	 {     
		 makeCurrent();
	
		 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glColor3f(0.0f, 1.0f, 0.0f);
	 
		resizeGL(width(), height());
		 if(stereo == true)
			 drawStereo();
		 else
			 draw();

		 QPainter painter(this);
		 painter.setRenderHint(QPainter::Antialiasing);
		 drawCursorInfo(&painter);
		 painter.end();
	 }

	 void GLWidget::drawStereo()
	 {    
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE); 

		// setUp shader
		stereoShaderProgram.bind();
		int inLeftColorLocation = stereoShaderProgram.uniformLocation("inLeftColor");
		stereoShaderProgram.setUniformValue(inLeftColorLocation, leftColor);	

		int inRightColorLocation = stereoShaderProgram.uniformLocation("inRightColor");
		stereoShaderProgram.setUniformValue(inRightColorLocation, rightColor);	

		int inHalfEyesWidthLocation = stereoShaderProgram.uniformLocation("inHalfEyesWidth");
		stereoShaderProgram.setUniformValue(inHalfEyesWidthLocation, eyesWidth/2.0f);
	
		// draw scene elements
		drawScene(stereoShaderProgram);

		glDisable(GL_BLEND);
	 }

	 void GLWidget::draw()
	 {
		 drawScene(mShaderProgram);
	 }

	 void GLWidget::drawScene(QGLShaderProgram & shaderProgram)
	 {
		 torus.value->DrawShader(shaderProgram, mViewMatrix, mProjectionMatrix);
		cursor->DrawShader(shaderProgram, mViewMatrix, mProjectionMatrix);

		for(int i=0; i < sceneElements.size(); ++i)
			sceneElements.at(i).data()->DrawShader(shaderProgram, mViewMatrix, mProjectionMatrix);
	
		b.DrawShader(c0BezierShaderProgram, mViewMatrix, mProjectionMatrix);
		b.DrawDeBore(shaderProgram, mViewMatrix, mProjectionMatrix);
	 }

	 void GLWidget::resizeGL(int width, int height)
	 {
		int side = qMin(width, height);
		glViewport(0,0, width, height);
	 }

	 void GLWidget::mousePressEvent(QMouseEvent *event)
	 {
		 lastPos = event->pos();
	 }

	 void GLWidget::mouseMoveEvent(QMouseEvent *event)
	 {
		 int dx = event->x() - lastPos.x();
		 int dy = event->y() - lastPos.y();

		 if (event->buttons() & Qt::LeftButton) {
			 setXRotation(dy);
			 setYRotation(dx);
		 } else if (event->buttons() & Qt::RightButton) {
			 setXRotation(dy);
			 setZRotation(dx);
		 }
		 lastPos = event->pos();
	 }

	 void GLWidget::keyPressEvent(QKeyEvent * event)
	 {
		 handleCursorInput(event);
		 float r;
		 switch(event->key())
		 {
		 case Qt::Key_A:
			 mViewMatrix.translate(-delta, 0.0, 0.0);
			 update();
			 break;
		 case Qt::Key_D:
			 mViewMatrix.translate(delta, 0.0, 0.0);
			 update();
			 break;
		 case Qt::Key_S:
			 mViewMatrix.translate(0.0, -delta, 0.0);
			 update();
			 break;
		 case Qt::Key_W:
			 mViewMatrix.translate(0.0, delta, 0.0);
			 update();
			 break;
		 case Qt::Key_Q:
			 mViewMatrix.translate(0.0, 0.0, delta);
			 update();
			 break;
		 case Qt::Key_Z:
			 mViewMatrix.translate(0.0, 0.0, -delta);
			 update();
			 break;
		 case Qt::Key_C:
				 torus = QSharedPointer<Torus>(new Torus(100, 100));
			 update();
			 break;
		 case Qt::Key_Plus:
			 r = torus.value->getBigRadius();
			 torus.value->setBigRadius(r + delta);
			 update();
			 break;
		 case Qt::Key_Minus:
			 r = torus.value->getBigRadius();
			 torus.value->setBigRadius(r - delta);
			 update();
			 break;
		 case Qt::Key_PageUp:
			 r = torus.value->getSmallRadius();
			 torus.value->setSmallRadius(r + delta);
			 update();
			 break;
		 case Qt::Key_PageDown:
			 r = torus.value->getSmallRadius();
			 torus.value->setSmallRadius(r - delta);
			 update();
			 break;
		 }
	 }

	 void GLWidget::wheelEvent ( QWheelEvent * event )
	 {
		int numDegrees = event->delta() / 8;
		int numSteps = numDegrees / 15;

		if (event->orientation() == Qt::Horizontal) {
			 mViewMatrix.translate(0.0, 0.0, -delta * numSteps);
			 update();
		} else {
			 mViewMatrix.translate(0.0, 0.0, delta * numSteps);
			 update();
		}
		event->accept();
	 }

	 bool GLWidget::prepareShaders()
	{
		if(initShader(mShaderProgram, 
				QString("./shaders/shader_frag.glsl"), 
				QString("./shaders/shader_vert.glsl"), 
				QString("./shaders/geom.glsl")) == false)
			return false;
		if(initShader(stereoShaderProgram, 
				QString("./shaders/stereoFrag.glsl"),
				QString("./shaders/stereoVert.glsl"),
				QString("./shaders/stereoGeom.glsl")) == false)
			return false;
		if(initShader(bezierShaderProgram, 
				QString("./shaders/bezierFrag.fp"), 
				QString("./shaders/bezierVert.vp"), 
				QString("./shaders/bezierGeom.geom")) == false)
			return false;
		if(initShader(c0BezierShaderProgram, 
				QString("./shaders/bezierC2/bSpline.fp"), 
				QString("./shaders/bezierC2/bSpline.vp"), 
				QString("./shaders/bezierC2/bSpline.geom")) == false)
			return false;
		return true;
	}

	bool GLWidget::initShader(QGLShaderProgram& shader, const QString& fragFile, const QString& vertFile, const QString& geomFile)
	{
		QGLShader shaderVert(QGLShader::Vertex);
		QGLShader shaderFrag(QGLShader::Fragment);
		QGLShader shaderGeom(QGLShader::Geometry);
		QString s;
		shader.setGeometryInputType(GL_LINES);
			s = shaderGeom.log();
			mShaderProgram.setGeometryOutputType(GL_LINES);
			s = shaderGeom.log();
		bool isCompiledGeometry = shaderGeom.compileSourceFile(geomFile);
		s = shaderGeom.log();
		bool isCompiledVert = shaderVert.compileSourceFile(vertFile);
		s = shaderVert.log();
		bool isCompiledFrag = shaderFrag.compileSourceFile(fragFile);
		s = shaderFrag.log();

		if(isCompiledVert && isCompiledFrag && isCompiledGeometry)
		{
			bool isAddVert = shader.addShader(&shaderVert);
			bool isAddFrag = shader.addShader(&shaderFrag);
			bool isAddGeom = shader.addShader(&shaderGeom);

			if(isAddVert && isAddFrag && isAddGeom)
			{
				shader.link();
				s = shader.log();
				return true;
			}
		}

		return false;
	}

	void GLWidget::setWidth(double width)
	{
		torus.value->setWidth(width);
		update();
	}
	void GLWidget::setHeight(double height)
	{
		torus.value->setHeight(height);
		update();
	}

	void GLWidget::setLength(double length)
	{
		torus.value->setLength(length);
		update();
	}

	void GLWidget::setTorusVerticesNumber(int verticesNumber)
	{
		int stripes = torus.value->numStripes;
		torus = QSharedPointer<Torus>(new Torus(verticesNumber, stripes));
		update();
	}

	void GLWidget::setTorusStripesNumber(int stripesNumber)
	{
		int vertices = torus.value->numVertices;
		torus = QSharedPointer<Torus>(new Torus(vertices, stripesNumber));
		update();
	}

	 void GLWidget::drawCursorInfo(QPainter *painter)
	 {
		 // scene position text
		 QVector3D scenePosition = (QVector4D((cursor->translation),1)).toVector3D();
		 QString text = tr("Cursor, scene pos\nx:").append(QString::number(scenePosition.x(), 'f', 3))
			 .append("\ny:").append(QString::number(scenePosition.y(), 'f', 3))
			 .append("\nz:").append(QString::number(scenePosition.z(), 'f', 3));
		 QFontMetrics metrics = QFontMetrics(font());
		 int border = qMax(4, metrics.leading());

		 int w = (width() - 2*border)*0.5;
		 QRect rect = metrics.boundingRect(0, 0, w, int(height()*0.125),
			 Qt::AlignLeft | Qt::TextWordWrap, text);
		 painter->setRenderHint(QPainter::TextAntialiasing);
		 painter->fillRect(QRect(0, 0, width(), rect.height() + 2*border),
						  QColor(0, 0, 0, 127));
		 painter->setPen(Qt::white);
		 painter->drawText(border, border,
						   rect.width(), rect.height(),
						   Qt::AlignLeft | Qt::TextWordWrap, text);
		 // screen position text
		 scenePosition = (mProjectionMatrix * mViewMatrix * QVector4D((cursor->translation),1)).toVector3DAffine();
		 text = tr("Cursor, screen pos\nx:").append(QString::number(scenePosition.x(), 'f', 3))
			 .append("\ny:").append(QString::number(scenePosition.y(), 'f', 3))
			 .append("\nz:").append(QString::number(scenePosition.z(), 'f', 3));
		 rect = metrics.boundingRect(w, 0, w, int(height()*0.125),
			 Qt::AlignLeft | Qt::TextWordWrap, text);
		 painter->drawText(w, border,
						   rect.width(), rect.height(),
						   Qt::AlignLeft | Qt::TextWordWrap, text);
	 }

	 void GLWidget::handleCursorInput(QKeyEvent * event)
	{
		 QVector3D offset;
		 bool moved = true;
		 switch(event->key())
		 {
		 case Qt::Key_Left:
			 cursor->translation = (mViewMatrix * QVector4D((cursor->translation),1)).toVector3D();
			cursor->translate(QVector3D(-cursorStep, 0, 0));
			cursor->translation = (mViewMatrix.inverted() * QVector4D((cursor->translation),1)).toVector3D();
			 update();
				 break;
		 case Qt::Key_Right:
			 cursor->translation = (mViewMatrix * QVector4D((cursor->translation),1)).toVector3D();
			cursor->translate(QVector3D(cursorStep, 0, 0));
			cursor->translation = (mViewMatrix.inverted() * QVector4D((cursor->translation),1)).toVector3D();
			 update();
				 break;
		 case Qt::Key_Up:
			 cursor->translation = (mViewMatrix * QVector4D((cursor->translation),1)).toVector3D();
			cursor->translate(QVector3D(0, cursorStep, 0));
			cursor->translation = (mViewMatrix.inverted() * QVector4D((cursor->translation),1)).toVector3D();
			 //cursor->translate(QVector3D(0, delta, 0));
			 update();
			 break;
		 case Qt::Key_Down:
			 cursor->translation = (mViewMatrix * QVector4D((cursor->translation),1)).toVector3D();
			cursor->translate(QVector3D(0, -cursorStep, 0));
			cursor->translation = (mViewMatrix.inverted() * QVector4D((cursor->translation),1)).toVector3D();
			 update();
			 break;
		case Qt::Key_P:
			 cursor->translation = (mViewMatrix * QVector4D((cursor->translation),1)).toVector3D();
			offset = cursorStep * ((cursor->translation - eyePosition).normalized());
			cursor->translation -= offset;
			cursor->translation = (mViewMatrix.inverted() * QVector4D((cursor->translation),1)).toVector3D();
			 update();
			 break;
		 case Qt::Key_O:
			 cursor->translation = (mViewMatrix * QVector4D((cursor->translation),1)).toVector3D();
			offset = cursorStep * ((cursor->translation - eyePosition).normalized());
			cursor->translation += offset;
			cursor->translation = (mViewMatrix.inverted() * QVector4D((cursor->translation),1)).toVector3D();
			//cursor->translate(delta*offset);
			 update();
			 break;
		case Qt::Key_Space:
			b.addDeBoreVertex(cursor->translation);
			update();
			break;
		case Qt::Key_T:
			b.addBezierVertex(cursor->translation);
			update();
			break;
		case Qt::Key_N:
			b.moveDeBoreTo(selectedIndex, cursor->translation);
			update();
			break;
		case Qt::Key_M:
			{
				b.deselectDeBorePoint(selectedIndex);
				QVector<int> points = b.getCloseDeBorePointsInRange(cursor->translation, cursor->range);
				if(points.count() > 0) // no points near cursor
				{
					b.selectDeBorePoint(points[0]);
					selectedIndex = points[0];
				}
				update();
			}
			//// mamy jakis punkt w zasiegu kursora
			//if(selectedElement.isNull() == false)
			//{
			//	// jezeli nie jest on chwycony
			//	if(grabbedElement.isNull())
			//	{
			//		grabbedElement = selectedElement;
			//		grabbedElement.data()->setColor(QColor(Qt::blue));
			//	}
			//	else // chwycony
			//	{
			//		grabbedElement.data()->translation = cursor->translation;
			//		grabbedElement.clear();
			//		selectedElement.clear();
			//	}
			//	update();
			//}
			break;
		case Qt::Key_Delete:
			if(grabbedElement.isNull() == false)
			{
				int indx = sceneElements.indexOf(grabbedElement);
				sceneElements.remove(indx);
				grabbedElement.clear();
				selectedElement.clear();
				//b.removeBezierVertex(indx);
				update();
			}
			break;
		default:
			moved = false;
		 }
		 if(moved && grabbedElement.isNull())
		{
			float minDist = 0.05f;
			QSharedPointer<Primitive> closestPrimitive(NULL);
			for(int i=sceneElements.size()-1; i >= 0 ; --i)
			{
				float dist = (sceneElements.at(i).data()->translation - cursor->translation).length();
				if(minDist > dist)
				{
					closestPrimitive = sceneElements.at(i);
					minDist = dist;
				}
			}
			bool notSame = closestPrimitive != selectedElement;
			if(closestPrimitive.isNull() || notSame)
				if(selectedElement.isNull() == false)
				{
					selectedElement.data()->setColor(Qt::white);
					selectedElement.clear();
				}
			if(closestPrimitive.isNull() == false && notSame)
			{
				closestPrimitive.data()->setColor(Qt::cyan);
				selectedElement = closestPrimitive;
			}
		}
	}
}